package roborally.model;

import java.math.BigDecimal;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class of energy amounts expressing an amount of energy
 * in units of a given unit for energy.
 * 	
 * @invar   ...
 *        	| isValidAmountInUnits(getAmountInUnits())
 * @invar   ...
 *        	| isValidUnit(getUnit())
 * 
 * @version  1.0
 * @author	Pieter Robberechts	(1e Bach Informatica)
 * @author	Jeroen Sanders 		(1e Bach Informatica)
 */
public class EnergyAmount {

	/**
	 * Initialize this new energy amount with given amount in units.
	 * 
	 * @param  	amountInUnits
	 *         	The amount in units for this new energy amount.
	 * @pre	   	...
	 * 		 	| isValidAmountInUnits(amountInUnits)	
	 * @pre		...
	 * 			| isValidUnit(unit)
	 * @effect 	...
	 *       	| setAmountInUnits(amountInUnits)
	 * @effect	...
	 * 			| setUnit(unit)      
	 */
	public EnergyAmount(double amountInUnits, EnergyUnit unit) {
		assert(isValidAmountInUnits(amountInUnits));
		assert(isValidUnit(unit));
		setAmountInUnits(amountInUnits);
		setUnit(unit);
	}

	/**
	 * Initialize this new energy amount with given amount, expressed in Ws.
	 * 
	 * @pre	   	...
	 * 		 	| isValidAmountInUnits(amountInUnits)
	 * @effect 	...
	 *       	| this(amountInUnits, EnergyUnit.Ws)
	 */
	public EnergyAmount(double amountInUnits) {
		this(amountInUnits, EnergyUnit.Ws);
		assert(isValidAmountInUnits(amountInUnits));
	}
	
	/**
	 * Initialize this new energy amount to 0 Ws.
	 * 
	 * @effect 	...
	 *       	| this(0, EnergyUnit.Ws)
	 */
	public EnergyAmount() {
		this(0, EnergyUnit.Ws);
	}

	/**
	 * Return the amount in units of this energy amount.
	 */
	@Basic
	@Raw
	public double getAmountInUnits() {
		return this.amountInUnits;
	}

	/**
	 * Check whether the given amount is a valid amount in units
	 * for any energy amount.
	 * 
	 * @param  	amount
	 *         	The amount to be checked.
	 * @return 	...
	 *       	| result == ( amount >= 0 && amount <= Double.MAX_VALUE )
	 */
	public static boolean isValidAmountInUnits(double amount) {
		return amount >= 0 && amount <= Double.MAX_VALUE;
	}

	/**
	 * Set the amount in units for this energy amount to the given amount
	 * in units.
	 * 
	 * @param  	amountInUnits
	 *         	The new amount in units for this energy amount.
	 * @pre    	...
	 * 		 	| isValidAmountInUnits(amountInUnits)        
	 * @post   	...
	 *       	| new.getAmountInUnits() == amountInUnits
	 */
	@Raw
	public void setAmountInUnits(double amountInUnits) {
		assert( isValidAmountInUnits(amountInUnits) );
		this.amountInUnits = amountInUnits;
	}

	/**
	 * Variable referencing the amount in units involved in
	 * this energy amount.
	 */
	private double amountInUnits;
	
	/**
	 * Return the unit of this energy amount.
	 */
	@Basic
	@Raw
	public EnergyUnit getUnit() {
		return unit;
	}
	
	/**
	 * Check whether the given unit is a valid unit
	 * for any energy amount.
	 * 
	 * @param  	unit
	 *         	The unit to be checked.
	 * @return 	...
	 *       	| result == (unit != null)
	 */
	public static boolean isValidUnit(EnergyUnit unit) {
		return unit != null;
	}

	/**
	 * Set the unit for this energy amount to the given unit.
	 * 
	 * @param  	unit
	 *         	The new unit for this energy amount.
	 * @pre		...
	 * 			| isValidUnit(unit)
	 * @post   	...
	 *       	| new.getUnit() == unit
	 */
	@Raw
	public void setUnit(EnergyUnit unit) {
		assert(isValidUnit(unit));
		this.unit = unit;
	}

	/**
	 * Variable referencing the unit of this energy amount.
	 */
	private EnergyUnit unit;


	/**
	 * Change the value of this energy amount to the given energy
	 * amount.
	 * 
	 * @param  	target
	 *         	The energy amount to be registered. 
	 * @pre	   	...
	 * 		 	| target != null
	 * @effect 	...
	 *       	| setAmountInUnits(target.getAmountInUnits())
	 * @effect	...
	 * 			| setUnit(target.getUnit())      
	 */
	public void changeValue(EnergyAmount target) {
		assert( target != null );
		setAmountInUnits(target.getAmountInUnits());
		setUnit(target.getUnit());
	}
	
	/**
	 * Return a new energy amount expressed in the given unit and
	 * with the same value as this energy amount.
	 * 
	 * @param  	unit
	 *         	The unit to convert to.
	 * @pre		...	
	 * 			| isValidUnit(unit)        
	 * @return 	...
	 *       	| (result != null) && (result != this)
	 * @return 	...
	 *       	| result.getUnit() == unit
	 * @return 	...
	 *       	| let
	 *      	|   conversion = BigInteger.valueOf(getUnit().toUnit(unit))
	 *       	| in
	 *       	|   result.getAmountInUnits() ==
	 *       	|     getAmountInUnits().multiply(conversion).
	 *       	|           divide(BigInteger.valueOf(10000000)).doubleValue()
	 */
	public EnergyAmount toUnit(EnergyUnit unit) {
		assert(isValidUnit(unit));
		BigDecimal conversion = getUnit().toUnit(unit);
		BigDecimal amount = new BigDecimal(getAmountInUnits());
		amount = amount.multiply(conversion);
		return new EnergyAmount(amount.toBigInteger().doubleValue(), unit);
	}


	/**
	 * Add the given energy amount to this energy amount.
	 * 
	 * @param  	other
	 *         	The energy amount to add.
	 * @pre    	...
	 *        	| other != null
	 * @pre		...
	 * 			| isValidAmountInUnits( this.getAmountInUnits() + 
	 * 			|	other.toUnit(this.getUnit()).getAmountInUnits() )       
	 * @return 	...
	 * 			| if( this.getUnit() != other.getUnit() )
	 * 			|	then result == add(other.toUnit(this.getUnit()))
	 * 			| else
	 *       	| 	then result == new EnergyAmount(getAmountInUnits() + 
	 *       	|							other.getAmountInUnits())
	 */
	public EnergyAmount add(EnergyAmount other) {
		assert( other != null );
		assert( isValidAmountInUnits(this.getAmountInUnits() + 
				other.toUnit(this.getUnit()).getAmountInUnits()) );
		if(this.getUnit() != other.getUnit())
			return add(other.toUnit(this.getUnit()));
		return new EnergyAmount(this.getAmountInUnits() + 
				other.getAmountInUnits(), this.getUnit());
	}
	
	/**
	 * Subtract the given energy amount from this energy amount.
	 * 
	 * @param  	other
	 *         	The energy amount to subtract.
	 * @pre    	...
	 *        	| other != null
	 * @pre		...
	 * 			| isValidAmountInUnits( this.getAmountInUnits() - 
	 * 			|		other.toUnit(this.getUnit()).getAmountInUnits() )      
	 * @return 	...
	 *       	| if( this.getUnit() != other.getUnit() )
	 * 			|	then result == subtract(other.toUnit(this.getUnit()))
	 * 			| else
	 *       	| 	then result == new EnergyAmount(getAmountInUnits() - 
	 *       	|							other.getAmountInUnits())
	 */
	public EnergyAmount subtract(EnergyAmount other) {
		assert( other != null );
		assert( isValidAmountInUnits(this.getAmountInUnits() - 
				other.toUnit(this.getUnit()).getAmountInUnits()) );
		if(this.getUnit() != other.getUnit())
			return subtract(other.toUnit(this.getUnit()));
		return new EnergyAmount(this.getAmountInUnits() - 
				other.getAmountInUnits(), this.getUnit());
	}
	
	/**
	 * Check whether this energy amount is less than the given energy amount.
	 * 
	 * @param  	other
	 *         	The energy amount to compare with.
	 * @pre    	...
	 *        	| other != null    
	 * @return 	...
	 *       	| if(this.getUnit() != other.getUnit())
	 *       	|	then result == lessThan(other.toUnit(this.getUnit()))
	 *       	| else
	 *       	|	then result == (this.getAmountInUnits() < other.getAmountInUnits())
	 */
	public boolean lessThan(EnergyAmount other) {
		assert( other != null );
		if(this.getUnit() != other.getUnit())
			return lessThan(other.toUnit(this.getUnit()));
		return (this.getAmountInUnits() < other.getAmountInUnits());
	}
	
	/**
	 * Check whether this energy amount is more than the given energy amount.
	 * 
	 * @param  	other
	 *         	The energy amount to compare with.
	 * @pre    	...
	 *        	| other != null    
	 * @return 	...
	 *       	| if(this.getUnit() != other.getUnit())
	 *       	|	then result == moreThan(other.toUnit(this.getUnit()))
	 *       	| else
	 *       	|	then result == (this.getAmountInUnits() > other.getAmountInUnits())
	 */
	public boolean moreThan(EnergyAmount other) {
		assert( other != null );
		if(this.getUnit() != other.getUnit())
			return moreThan(other.toUnit(this.getUnit()));
		return (this.getAmountInUnits() > other.getAmountInUnits());
	}
	

	/**
	 * Return a new energy amount that is identical to this energy amount.
	 * 
	 * @return 	...
	 *       	| (result != null) && (result != this) &&
	 *       	| (result.getAmountInUnits() == this.getAmountInUnits()) &&
	 *       	| (result.getUnit() == this.getUnit())
	 */
	public EnergyAmount getCopy() {
		return new EnergyAmount(getAmountInUnits(), getUnit());
	}

	/**
	 * Check whether this energy amount is identical to the
	 * given energy amount.
	 * 
	 * @param  	other
	 *         	The energy amount to compare with.
	 * @return 	...
	 *       	| result ==
	 *       	|   (other != null) &&
	 *       	|   (getAmountInUnits() == other.getAmountInUnits()) &&
	 *       	|	(getUnit() == other.getUnit())
	 */
	public boolean isIdenticalTo(EnergyAmount other) {
		return (other != null)
				&& (getAmountInUnits() == other.getAmountInUnits())
				&& (getUnit() == other.getUnit());
	}

	/**
	 * Return a textual representation of this energy amount.
	 * 
	 * @return 	...
	 *       	| result.equals( getAmountInUnits() + getUnit().toString() )
	 */
	public String toString() {
		return getAmountInUnits() + " " + getUnit().toString();
	}
}
